﻿using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections.Generic;
using System.Linq;

namespace Lhz.Test.Common
{
    public abstract class CrudTester<TPk, TEntity>
    {
        public void CrudTest(TEntity entity, TPk pk, Dictionary<string, object> changes = null, string[] paths = null)
        {
            var entityType = typeof(TEntity);

            if (pk != null)
            {
                Assert.IsNull(QueryByPk(pk, paths));
            }

            Add(entity);
            var dbEntity = QueryByPk(pk, paths);
            entity = dbEntity;
            Assert.IsNotNull(dbEntity);
            Assert.IsTrue(dbEntity.AreSame(entity));

            if (changes != null)
            {
                foreach (var change in changes)
                {
                    entityType.GetProperty(change.Key).SetValue(entity, change.Value);
                }
            }

            Update(entity);

            if (changes != null)
            {
                dbEntity = QueryByPk(pk, paths);
                entity = dbEntity;
                Assert.IsNotNull(dbEntity);
                foreach (var change in changes)
                {
                    Assert.AreEqual(entityType.GetProperty(change.Key).GetValue(dbEntity), change.Value);
                }
            }

            Delete(entity);
            dbEntity = QueryByPk(pk, paths);
            Assert.IsNull(dbEntity);
        }

        public void CrudWithOneToManyTest<TChildPk, TChild>(CascadeMode mode, TEntity entity, TPk pk, string propertyName, Dictionary<TChildPk, TChild> children, string fkPropertyName, CrudTester<TChildPk, TChild> ctChild)
        {
            var entityType = typeof(TEntity);
            foreach (var child in children)
            {
                child.Value.GetType().GetProperty(fkPropertyName).SetValue(child.Value, pk);
            }
            var collectionProperty = entityType.GetProperty(propertyName);
            var collection = collectionProperty.GetValue(entity);
            if (collection == null)
            {
                collectionProperty.SetValue(entity, new List<TChild>());
                collection = collectionProperty.GetValue(entity);
            }
            Assert.IsNotNull(collection);
            foreach (var child in children)
            {
                (collection as IList<TChild>).Add(child.Value);
            }
            Add(entity);
            foreach (var child in children)
            {
                switch (mode)
                {
                    case CascadeMode.None:
                        Assert.IsNull(ctChild.QueryByPk(child.Key));
                        break;
                    case CascadeMode.AllDeleteOrphan:
                        Assert.IsNotNull(ctChild.QueryByPk(child.Key));
                        break;
                    default:
                        Assert.Fail("未知级联模式【" + mode + "】。");
                        break;
                }
            }

            if (mode == CascadeMode.AllDeleteOrphan)
            {
                var dbEntity = QueryByPk(pk, new string[] { propertyName });
                collection = collectionProperty.GetValue(dbEntity);
                Assert.IsTrue((collection as IEnumerable<TChild>).Count() > 0);
                (collectionProperty.GetValue(entity) as IList<TChild>).Clear();
                Update(entity);
                foreach (var child in children)
                {
                    Assert.IsNull(ctChild.QueryByPk(child.Key));
                }

                dbEntity = QueryByPk(pk, new string[] { propertyName });
                collection = collectionProperty.GetValue(dbEntity);
                Assert.IsTrue((collection as IEnumerable<TChild>).Count() == 0);
                foreach (var item in children.Select(r => r.Value))
                {
                    (collectionProperty.GetValue(entity) as IList<TChild>).Add(item);
                }
                Update(entity);
                foreach (var child in children)
                {
                    Assert.IsNotNull(ctChild.QueryByPk(child.Key));
                }

                dbEntity = QueryByPk(pk, new string[] { propertyName });
                collection = collectionProperty.GetValue(dbEntity);
                Assert.IsTrue((collection as IEnumerable<TChild>).Count() > 0);
                Delete(entity);
                foreach (var child in children)
                {
                    Assert.IsNull(ctChild.QueryByPk(child.Key));
                }
            }
        }

        public void CrudWithManyToManyTest<TChildPk, TChild>(CascadeMode mode, TEntity entity, TPk pk, string propertyName, Dictionary<TChildPk, TChild> children, CrudTester<TChildPk, TChild> ctChild)
        {
            var entityType = typeof(TEntity);
            var collectionProperty = entity.GetType().GetProperty(propertyName);
            var collection = collectionProperty.GetValue(entity);
            if (collection == null)
            {
                collectionProperty.SetValue(entity, new List<TChild>());
                collection = collectionProperty.GetValue(entity);
            }
            Assert.IsNotNull(collection);
            foreach (var child in children)
            {
                (collection as IList<TChild>).Add(child.Value);
            }
            Add(entity);
            foreach (var child in children)
            {
                switch (mode)
                {
                    case CascadeMode.None:
                        break;
                    case CascadeMode.AllDeleteOrphan:
                        Assert.IsNotNull(ctChild.QueryByPk(child.Key));
                        break;
                    default:
                        Assert.Fail("未知级联模式【" + mode + "】。");
                        break;
                }
            }

            if (mode == CascadeMode.AllDeleteOrphan)
            {
                var dbEntity = QueryByPk(pk, new string[] { propertyName });
                collection = collectionProperty.GetValue(dbEntity);
                Assert.IsTrue((collection as IEnumerable<TChild>).Count() > 0);
                (collectionProperty.GetValue(entity) as IList<TChild>).Clear();
                Update(entity);
                foreach (var child in children)
                {
                    Assert.IsNull(ctChild.QueryByPk(child.Key));
                }

                dbEntity = QueryByPk(pk, new string[] { propertyName });
                collection = collectionProperty.GetValue(dbEntity);
                Assert.IsTrue((collection as IEnumerable<TChild>).Count() == 0);
                foreach (var item in children.Select(r => r.Value))
                {
                    (collectionProperty.GetValue(entity) as IList<TChild>).Add(item);
                }
                Update(entity);
                foreach (var child in children)
                {
                    Assert.IsNotNull(ctChild.QueryByPk(child.Key));
                }

                dbEntity = QueryByPk(pk, new string[] { propertyName });
                collection = collectionProperty.GetValue(dbEntity);
                Assert.IsTrue((collection as IEnumerable<TChild>).Count() > 0);
                Delete(entity);
                foreach (var child in children)
                {
                    Assert.IsNull(ctChild.QueryByPk(child.Key));
                }
            }
        }

        protected abstract TEntity QueryByPk(TPk pk, string[] loadPaths = null);

        protected abstract void Add(TEntity entity);

        protected abstract void Update(TEntity entity);

        protected abstract void Delete(TEntity entity);
    }
}
